package conn

import (
	"crypto/tls"
	"fmt"
	mqtt "github.com/eclipse/paho.mqtt.golang"
	"github.com/kubeedge/mappers-go/mappers/common"
	"time"
)

type MqttClient struct {
	subTopic     string
	subOnMessage mqtt.MessageHandler
	common.MqttClient
}

func (mc *MqttClient) StrongConnect(qos ...byte) error {

	mqttOpt := mqtt.NewClientOptions().SetKeepAlive(6 * time.Second).
		SetConnectRetry(true).
		SetConnectRetryInterval(5 * time.Second).
		SetCleanSession(true).
		SetAutoReconnect(true).
		SetConnectionLostHandler(func(mqtt.Client, error) {
			fmt.Println("connection lost:")
		}).
		SetReconnectingHandler(func(client mqtt.Client, options *mqtt.ClientOptions) {
			fmt.Println("attempting to reconnect")
		}).
		SetOnConnectHandler(func(client mqtt.Client) {
			fmt.Println("connection established")
			if mc.subTopic != "" && mc.subOnMessage != nil {
				_ = mc.StrongSubscribe(mc.subTopic, mc.subOnMessage)
			}
		})

	mqttClientOpts := []*mqtt.ClientOptions{
		mqttOpt,
	}

	if err := mc.Connect(mqttClientOpts, qos...); err != nil {
		return err
	}
	return nil
}

func (mc *MqttClient) Connect(ops []*mqtt.ClientOptions, qos ...byte) error {

	opts := mqtt.NewClientOptions().AddBroker(mc.IP).SetClientID("").SetCleanSession(true)
	if mc.Cert != "" {
		tlsConfig, err := newTLSConfig(mc.Cert, mc.PrivateKey)
		if err != nil {
			return err
		}
		opts.SetTLSConfig(tlsConfig)
	} else {
		opts.SetUsername(mc.User)
		opts.SetPassword(mc.Passwd)
	}

	if len(ops) > 0 {
		for _, op := range ops {
			op := op
			switch {
			case op.ConnectRetry:
				opts.SetConnectRetry(op.ConnectRetry)
				fallthrough
			case op.ConnectRetryInterval > 0 && op.ConnectRetryInterval != 30*time.Second:
				opts.SetConnectRetryInterval(op.ConnectRetryInterval)
				fallthrough
			case op.KeepAlive > 0 && op.KeepAlive != 30:
				opts.SetKeepAlive(time.Duration(op.KeepAlive) * time.Second)
				fallthrough
			case !op.AutoReconnect:
				opts.SetAutoReconnect(op.AutoReconnect)
				fallthrough
			case !op.CleanSession:
				opts.SetCleanSession(op.CleanSession)
				fallthrough
			case op.OnConnect != nil:
				opts.SetOnConnectHandler(op.OnConnect)
				fallthrough
			case op.OnConnectionLost != nil:
				opts.SetConnectionLostHandler(op.OnConnectionLost)
				fallthrough
			case op.OnReconnecting != nil:
				opts.SetReconnectingHandler(op.OnReconnecting)
				fallthrough
			default:

			}
		}
	}

	mc.Client = mqtt.NewClient(opts)
	// The token is used to indicate when actions have completed.
	if tc := mc.Client.Connect(); tc.Wait() && tc.Error() != nil {
		return tc.Error()
	}

	if len(qos) > 0 {
		mc.Qos = qos[0]
	} else {
		mc.Qos = 0 // At most 1 time
	}
	mc.Retained = false // Not retained
	return nil
}

// newTLSConfig new TLS configuration.
// Only one side check. Mqtt broker check the cert from client.
func newTLSConfig(certfile string, privateKey string) (*tls.Config, error) {
	// Import client certificate/key pair
	cert, err := tls.LoadX509KeyPair(certfile, privateKey)
	if err != nil {
		return nil, err
	}

	// Create tls.Config with desired tls properties
	return &tls.Config{
		// ClientAuth = whether to request cert from server.
		// Since the server is set up for SSL, this happens
		// anyways.
		ClientAuth: tls.NoClientCert,
		// ClientCAs = certs used to validate client cert.
		ClientCAs: nil,
		// InsecureSkipVerify = verify that cert contents
		// match server. IP matches what is in cert etc.
		InsecureSkipVerify: true,
		// Certificates = list of certs client sends to server.
		Certificates: []tls.Certificate{cert},
	}, nil
}

func (mc *MqttClient) StrongSubscribe(topic string, onMessage mqtt.MessageHandler) error {
	mc.subTopic = topic
	mc.subOnMessage = onMessage
	return mc.Subscribe(mc.subTopic, mc.subOnMessage)
}

